﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Collections;
using Noris.Tools.TraceVisualiser.Components;
using Noris.Tools.TraceVisualiser.Support;

namespace Noris.Tools.TraceVisualiser.TraceData
{
    public class TraceDataSource : IDataSource
    {
        #region IDataSource Members
		/// <summary>
		/// Vlastnosti datového zdroje. Jde o statické informace.
		/// </summary>
		DataSourceProperties IDataSource.Properties
        {
            get
            {
                return new DataSourceProperties(){
                    SourceName = "TraceVizualizer" 
                };
            }
        }
		/// <summary>
		/// Zde datový zdroj deklaruje, jaké grafy chce zobrazit.
		/// Vizuální vrstva deklarace převezme a založí odpovídající grafy, a naváže do nich tento datový zdroj.
		/// </summary>
		List<GraphDeclarationCls> IDataSource.GraphDeclarations { get { return _GetGraphDeclarations(); } }
		/// <summary>
		/// Vstupní metoda, skrze kterou je datový zdroj požádán o zpracování požadavku.
		/// Požadavek může nést různá data, a může vracet různá data.
		/// Tato metoda je volána v asynchronním threadu (na pozadí), datový zdroj nijak neřeší threading.
		/// Synchronizaci práce datového zdroje a grafu řeší někdo jiný (AsyncWorker).
		/// Je zajištěno, že tato metoda není volána multithreadově (z různých vláken najednou).
		/// Nicméně je volána na jiném threadu, než na kterém byl objekt datového zdroje vytvořen.
		/// </summary>
		/// <param name="request">Požadovaná operace</param>
		void IDataSource.RunRequest(IDataSourceRequest request)
        {
            switch (request.RequestType)
            {
				case DataSourceRequestType.Initialize:
					this._Initialize(request as DataRequestInitialize);
                    break;
                case DataSourceRequestType.ReadRows:
                    this._ReadRows(request as DataRequestReadRows);
                    break;
                case DataSourceRequestType.ReadElementsForRows:
                    this._ReadElements(request as DataRequestReadElementsForRows);
                    break;
                case DataSourceRequestType.ReadSpecific:
                    this._ReadSpecific(request as DataRequestReadSpecific);
                    break;
            }
        }
		/// <summary>
		/// Event, který datový zdroj vyvolá tehdy, když se změní data (například vlivem nastavení) a datový zdroj sám aktivně žádá o nějakou akci do grafické vrstvy.
		/// Typicky: přenačtení dat, změny rozsahu, a jiné změny.
		/// </summary>
		event DataSourceEventHandler IDataSource.DataChanged
        {
            add { _DataChanged += value; }
            remove { _DataChanged -= value; }
        }
        private event DataSourceEventHandler _DataChanged;
        /// <summary>
        /// Disposable
        /// </summary>
        public void Dispose()
        {
            if (this.Main != null)
                this.Main.Dispose();
        }
        #endregion
        #region PŘÍSTUP K DATŮM
        /// <summary>
        /// Přístup k datům trace
        /// </summary>
        public MainInfo Main { get { return _Main; } }
        private MainInfo _Main;
        #endregion
        #region GRAPH DECLARATIONS
        private List<GraphDeclarationCls> _GetGraphDeclarations()
		{
			List<GraphDeclarationCls> result = new List<GraphDeclarationCls>();

			GraphDeclarationCls g1 = new GraphDeclarationCls();
			g1.DataSource = this;
			g1.DefaultGraphMode = RowGraphMode.MultiTask;
			g1.FragmentLineLayer = GraphLayerType.UnderItemLayer;
			g1.GraphCaption = "Trace data";
			g1.RowClassNumber = 10;
			g1.RowColor = Color.DarkBlue;
            // Výšky řádků lze definovat jednak v deklaraci grafu, a jednak v řádku. Řádek má přednost.
			g1.RowHeightAddLineFragmentRatio = 0.10M;        // Prázdný prostor v řádkem nahoře nad posledním elementem, pokud se budou používat elementy s UseRatio != 1M
            g1.RowHeightAddLineSolidRatio = 0.25M;           // Prázdný prostor v řádkem nahoře nad posledním elementem, pokud se budou používat elementy s UseRatio == 1M
            g1.RowHeightLineFragmentPx = 32;                 // Výška elementu s UseRatio == 1M, když se najdou jiné elementy s UseRatio != 1M
            g1.RowHeightLineSolidPx = 16;                    // Výška elementu s UseRatio == 1M, když žádný element nebude mít výšku podílovou (UseRatio != 1M)
            g1.RowHeightMinFragmentPx = 8;                   // Pokud najdu element s UseRatio < 1M, pak zde lze určit nejmenší počet pixelů výšky takového malého elementu
            g1.RowHeightTotalMinPx = 25;                     // Nejmenší výška řádku, pokud by se nenašel žádný element...
            g1.RowHeightTotalMaxPx = 200;                    // Nejvyšší výška řádku, pokud by vyšel větší, pak se vše proporcionálně zmenší
			g1.TabColor = Color.DarkBlue;
			g1.TimeTickLayer = GraphLayerType.UnderItemLayer;

			g1.Columns = new List<GraphColumnDeclarationCls>();
			GraphColumnDeclarationCls c;

			c = new GraphColumnDeclarationCls();
			c.Key = "title";
			c.Text = "Údaj";
			c.RowFilterVisible = true;
			c.HeaderMode = DColumnHeaderMode.StandardHeader;
			c.Width = 200;
			c.MinimumWidth = 35;
			c.MaximumWidth = 750;
			c.Alignment = ContentAlignment.MiddleCenter;
            c.ContentAlignment = ContentAlignment.MiddleLeft;
			c.Tag = null;
			g1.Columns.Add(c);

            /*    Sloupec "time" nepoužíváme:
            c = new GraphColumnDeclarationCls();
            c.Key = "time";
            c.Text = "Čas [ms]";
            c.RowFilterVisible = true;
            c.HeaderMode = DColumnHeaderMode.StandardHeader;
            c.Width = 90;
            c.MinimumWidth = 35;
            c.MaximumWidth = 160;
            c.Alignment = ContentAlignment.MiddleCenter;
            c.ContentAlignment = ContentAlignment.MiddleRight;
            c.Tag = null;
            g1.Columns.Add(c);
            */

            c = new GraphColumnDeclarationCls();
			c.Key = "timeaxis";
			c.Text = "";
			c.RowFilterVisible = false;
			c.HeaderMode = DColumnHeaderMode.TimeAxis;
			c.ResizeWidthToFillParent = true;
			c.Width = 500;
			c.MinimumWidth = 200;
			c.MaximumWidth = null;
			c.Alignment = ContentAlignment.MiddleCenter;
			c.Tag = null;
			g1.Columns.Add(c);

			result.Add(g1);

			return result;
		}
		#endregion
		#region Inicializace datového zdroje a grafu
		private void _Initialize(DataRequestInitialize request)
		{
			if (request == null) return;
            TraceDocument document = request.UserData as TraceDocument;
			if (document == null) return;
			this._Main = new MainInfo();
            this._Main.OpenDocument(document);
			this._Main.DataChanged += new DataSourceEventHandler(_Main_DataChanged);

            // Určit InitialTimeRange:
            if (request.InitialTimeRange.IsEmpty)
            {
                TimeRange setTimeRange = this._Main.TimeInitial;
                request.InitialTimeRange = setTimeRange.Scale(1.25D, 0.90D);
            }

            // Načíst trace:
            if (request.LoadInitialTraces)
                this._Main.CheckLoadContentForTime(request.InitialTimeRange, null);

			request.ReloadAllRows = true;

            // Zahájit řízení volnočasové aktivity:
            this._AsyncWorkFreeTimeStart();
        }
		/// <summary>
		/// Handler eventu this._Main.DataChanged : 
		/// Main je výkonný datový zdroj, 
		/// zdejší třída TraceDataSource je konektor mezi daty na jedné straně a vizuální vrstvou na straně druhé.
		/// Pokud data (Main) vygenerují samostatnou událost DataChanged, zdejší eventhandler ji předá do vizuální vrstvy.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		void _Main_DataChanged(object sender, DataSourceEventArgs args)
		{
			this.OnDataChanged(args);
		}
		/// <summary>
		/// Invoker eventu DataChanged
		/// </summary>
		/// <param name="e"></param>
		protected virtual void OnDataChanged(DataSourceEventArgs e)
		{
			if (this._DataChanged != null)
				this._DataChanged(this, e);
		}
		#endregion
        #region Služby datového zdroje nad rámec IDataSource
        /// <summary>
        /// Uvolní paměť.
        /// </summary>
        internal void MemoryFree()
        {
            this.Main.MemoryFree();
        }
        #endregion
        #region Volnočasová aktivita
        /// <summary>
        /// Napojí AsyncWorker, jeho volný čas, na zdejší objekt. My budeme řídit, jakou aktivitu bude AsyncWorker vyvíjet, když po něm neude nic požadovat uživatel.
        /// Budeme si přednačítat naše soubory.
        /// </summary>
        private void _AsyncWorkFreeTimeStart()
        {
            if (this.Main.Document.PreLoadFiles)
                AsyncWorker.FreeTimeActivitySet(this._AsyncWorkFreeTimeRun, 5D);   // Vždy po 5 sekundách, kdy AsyncWorker nemá nic na práci, zavolá zdejší metodu _AsyncWorkFreeTimeRun. Ta mu zkusí práci najít.
        }
        /// <summary>
        /// Zkusí najít nějakou práci pro AsyncWorker. On má volný čas a něco by rád dělal.
        /// </summary>
        /// <returns></returns>
        private IAsyncRequest _AsyncWorkFreeTimeRun()
        {
            TimeVector vector = this._TimeRangeChangeVector;
            GID traceFile = this._Main.FindFirstTraceToPrepare(vector);
            if (traceFile.IsEmpty) return null;
            DataRequestReadSpecific request = new DataRequestReadSpecific(((IDataSource)this).RunRequest, null, null, TimeRange.Empty, null, traceFile);
            return request;
        }
        /// <summary>
        /// Čas aktuálního požadavku. Setování zajistí posunutí předchozí hodnoty do _TimeRangePreviousRequest.
        /// </summary>
        private TimeRange _TimeRangeRequest
        {
            get { return this._TimeRangeCurrentRequest; }
            set
            {
                if (value == this._TimeRangeCurrentRequest) return;          // Není změna => nic neměníme.
                if (value.Within(this._TimeRangeCurrentRequest)) return;     // Pokud nový interval je celý uvnitř Current, tak to nás nezajímá => skončíme. Nový interval musí alespoň na nějakou stranu přesahovat.
                this._TimeRangePreviousRequest = this._TimeRangeCurrentRequest;
                this._TimeRangeCurrentRequest = value;
            }
        }
        /// <summary>
        /// Vektor změny = pohybu uživatele po časové ose
        /// </summary>
        private TimeVector _TimeRangeChangeVector
        {
            get
            {
                TimeRange current = this._TimeRangeCurrentRequest;
                if (current.IsEmpty) return TimeVector.Empty;
                TimeRange previous = this._TimeRangePreviousRequest;
                if (previous.IsEmpty) return new TimeVector(current.Begin, TimeRange.TimeDirection.ToHistory);
                
                // Máme časové intervaly Current i Previous:
                long diffB = ((TimeSpan)(current.Begin - previous.Begin)).Ticks;
                long diffE = ((TimeSpan)(current.End - previous.End)).Ticks;
                if (diffB < 0L && diffE <= 0L)       // Current se posouvá ToHistory:
                    return new TimeVector(current.Begin, TimeRange.TimeDirection.ToHistory);
                if (diffE > 0L && diffB >= 0L)       // Current se posouvá ToFuture:
                    return new TimeVector(current.End, TimeRange.TimeDirection.ToFuture);

                // Current se posouvá na obou stranách (Begin i End):
                if (diffB < 0L && diffE > 0L)        // Current se zvětšuje (roste jeho velikost):
                {
                    if ((-diffB) >= diffE)           // Posun Current.Begin ToHistory je větší (nebo stejný), než posun Current.End ToFuture:
                        return new TimeVector(current.Begin, TimeRange.TimeDirection.ToHistory);
                    else
                        return new TimeVector(current.End, TimeRange.TimeDirection.ToFuture);
                }
                // Current se zmenšuje:
                return new TimeVector(current.Begin, TimeRange.TimeDirection.ToHistory);
            }
        }
        /// <summary>
        /// Čas aktuálného requestu ReadElements
        /// </summary>
        private TimeRange _TimeRangeCurrentRequest;
        /// <summary>
        /// Čas předchozího requestu ReadElements, odlišný od _TimeRangeCurrentRequest;
        /// </summary>
        private TimeRange _TimeRangePreviousRequest;
		#endregion
        #region Read rows
        /// <summary>
        /// Čtení řádek obecné
        /// </summary>
        /// <param name="request"></param>
        private void _ReadRows(DataRequestReadRows request)
        {
			if (request == null) return;
			if (this._Main == null) return;

            request.DataRows = new List<IDataRow>();

			if (!request.ParentRowGid.IsEmpty)
				_ReadRowsSubNodes(request);
			else if (!request.RefreshRowGId.IsEmpty)
				_ReadRowsRefresh(request);
			else if (!request.GraphRowGid.IsEmpty)
				_ReadRowsMain(request);
        }
        #region Čtení řádků Main úrovně
        /// <summary>
		/// Vrátí Main řádek = jeden řádek, obsahuje elementy = trace soubory
		/// </summary>
		/// <param name="request"></param>
		private void _ReadRowsMain(DataRequestReadRows request)
		{
			request.DataRows.AddRange(this._Main.NodeRoot.CreateChildRows());
            this._Main.NodeRoot.Root.CollectChanges(true);      // Zajistím že se do dat (datová vrstva = TraceData) vepíše, že nejsou žádné změny.
		}
        #endregion
        #region Čtení řádků SubNodes = podřízené k danému řádku
        /// <summary>
		/// Vrací SubNodes řádky
		/// </summary>
		/// <param name="request"></param>
		private void _ReadRowsSubNodes(DataRequestReadRows request)
		{
            // Před načítáním řádků SubNodes musím zajistit to, že budou načteny trace soubory pro požadovaný čas a řádek:
            // Tím budu naplněny seznamy elementů v odpovídajících řádcích.
			// Současně budou doplněny seznamy SubNodes (=Childs) nodu.
            this._CheckLoadContent(request.TimeRange, request.ParentRowGid);

			NodeInfo nodeInfo = this._Main.FindNode(request.ParentRowGid);
			if (nodeInfo != null)
				request.DataRows.AddRange(nodeInfo.CreateChildRows(request.TimeRange));

			// Do requestu přidat i data o nových nebo změněných položkách:
            this._CollectChanges(request, GID.Empty, request.ParentRowGid);
		}
        #endregion
        #region Čtení konkrétního řádku = refresh
        /// <summary>
		/// Vrátí řádek pro Refresh
		/// </summary>
		/// <param name="request"></param>
		private void _ReadRowsRefresh(DataRequestReadRows request)
		{
		}
        #endregion
        #endregion
        #region Read elements => najít node a on to zajistí
        private void _ReadElements(DataRequestReadElementsForRows request)
        {
			if (request == null) return;

            // Zjistíme, jaký řádek si ty elementy žádá (ne kvůli každému budeme skutečně načítat data !!!):
            NodeInfo node = this._Main.FindNode(request.RowGid);
            if (node == null) return;

			if (node.Parent.GId == this._Main.NodeRoot.GId)
			{	// Pro řádky první úrovně určitě načítat nebudeme nic, to je jejich účel !!!
				if (node.GId == this._Main.NodeTrace.GId)
				{   // Pro řádek NodeTrace elementy načtu, protože se mohly smazat:
					request.DataRowElements = node.CreateRowElementList(request.TimeRange);
				}

				return;
			}
            // Zde si uložím TimeRange aktuálního requestu. V SET accessoru se detekuje pohyb v časové ose, a ukládá se čas a vektor pro očekávaný pohyb budoucí.
            this._TimeRangeRequest = request.TimeRange;

			// Před načítáním elementů daného řádku musím zajistit to, že budou načteny trace soubory pro požadovaný čas a řádek:
            this._CheckLoadContent(request.TimeRange, request.RowGid);

            // Pak z dříve již nalezeného nodu vytáhnu elementy za požadovaný čas:
            request.DataRowElements = node.CreateRowElementList(request.TimeRange);

            // Do requestu přidat i data o nových nebo změněných položkách:
            this._CollectChanges(request, request.RowGid, GID.Empty);
        }
        #endregion
        #region Zajištění načtení obsahu trace souborů pro daný časový interval a daný řádek
        private void _CheckLoadContent(TimeRange time, GID rowGId)
        {
			NodeInfo nodeInfo = this._Main.FindNode(rowGId);                      // Najdu node = řádek, kvůli kterému se provede načítání obsahu
			string traceType = (nodeInfo == null ? null : nodeInfo.TraceType);    // Určím typ trace = pro výběr trace souborů, které se budoui reálně načítat
            this._Main.CheckLoadContentForTime(time, traceType);                  // Zajistím načtení těch Trace souborů, které obsahují data za daný čas a pro daný Node
        }
        #endregion
        #region Read specific traceFile
        /// <summary>
        /// Zajistí načtení dat jednoho Trace souboru, do vizuální vrstvy nic nevrací = nejde o vizuální požadavek, ale o volnočasovou aktivitu.
        /// </summary>
        /// <param name="dataRequestReadSpecific"></param>
        private void _ReadSpecific(DataRequestReadSpecific request)
        {
            this._Main.LoadContentForTraceFile(request.TraceFileGId);
        }
        #endregion
        #region CollectChanges
        /// <summary>
		/// Do requestu (který je přinejmenším typu DataRequestRows = tedy jak DataRequestReadRows, tak DataRequestReadElementsForRows)
		/// zajistí vepsání všech řádků, které se změnily anebo přibyly.
		/// Nebude vepisovat řádek, který má GID == exceptRowGId (protože ten je cílem requestu, a nebude se tedy invalidovat),
		/// a nebude vepisovat které mají ParentGID == exceptParentRowGId, protože v tomto requestu jsme právě tyto řádky načítali.
		/// </summary>
		/// <param name="request"></param>
		/// <param name="exceptRowGId"></param>
		/// <param name="exceptParentRowGId"></param>
		private void _CollectChanges(DataRequestRows request, GID exceptRowGId, GID exceptParentRowGId)
        {
            // Načtu změny, a rovnou je potvrdím jako převzaté (smažu z řádků příznaky, které tuto změnu signalizují):
			NodeChangeCollect changeCollect = this._Main.NodeRoot.Root.CollectChanges(true);

			// GID řádků se změnou elementů:
			if (request.InvalidateContentRows == null)
				request.InvalidateContentRows = new List<GID>();
			foreach (GID rowGId in changeCollect.NodeWithElementChangedList)
			{
				if (rowGId != exceptRowGId)
					request.InvalidateContentRows.Add(rowGId);
			}

			// Nově přidané řádky:
			if (request.DataRows == null)
				request.DataRows = new List<IDataRow>();
			foreach (IDataRow row in changeCollect.NodeNewcomerList)
			{
				if (row.GId != exceptRowGId && row.ParentGId != exceptParentRowGId)
					request.DataRows.Add(row);
			}
        }
        #endregion
    }
}
